package com.emeraldparser.symbol;

import com.emeraldparser.util.Automaton;
import com.emeraldparser.util.Automatons;
import com.emeraldparser.util.HashCodes;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;

/**
 * A symbol class that may represent any of several other symbol classes.
 * 
 * @author Adam Paynter
 *
 */
public final class AlternationSymbolClass extends TwoStateSymbolClass {

	private final ImmutableSet<? extends SymbolClass> alternatedSymbolClasses;
	private final transient int hashCode;

	public AlternationSymbolClass(SymbolClass... alternatedSymbolClasses) {
		this(ImmutableSet.copyOf(alternatedSymbolClasses));
	}

	public AlternationSymbolClass(Iterable<? extends SymbolClass> alternatedSymbolClasses) {
		this(ImmutableSet.copyOf(alternatedSymbolClasses));
	}

	public AlternationSymbolClass(ImmutableSet<? extends SymbolClass> alternatedSymbolClasses) {
		this.alternatedSymbolClasses = Preconditions.checkNotNull(alternatedSymbolClasses, "Alternated symbol classes must not be null");
		this.hashCode = HashCodes.classSpecificHashCode(AlternationSymbolClass.class, alternatedSymbolClasses);
	}

	public ImmutableSet<? extends SymbolClass> getAlternatedSymbolClasses() {
		return alternatedSymbolClasses;
	}

	@Override
	public Automaton<? extends State, ? extends SymbolClass> toAutomaton() {
		Automaton<State, SymbolClass> automaton = Automatons.create(super.toAutomaton());
		
		for (SymbolClass symbolClass : alternatedSymbolClasses) {
			automaton.addDirectedEdge(getBeforeState(), getAfterState(), symbolClass);
		}
		
		return automaton;
	}

	@Override
	public String toString() {
		return "(" + Joiner.on(" | ").join(alternatedSymbolClasses) + ")";
	}

	@Override
	public int hashCode() {
		return hashCode;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		
		if (obj == null) {
			return false;
		}
		
		if (getClass() != obj.getClass()) {
			return false;
		}
		
		AlternationSymbolClass other = (AlternationSymbolClass) obj;
		
		return Objects.equal(alternatedSymbolClasses, other.alternatedSymbolClasses);
	}

	@Override
	public <I, O> O accept(SymbolClassVisitor<I, O> visitor, I input) {
		return visitor.visitAlternationSymbolClass(this, input);
	}

}
